package com.example.redis.demo.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.redis.demo.dto.UserGeoDTO;
import com.example.redis.demo.dto.UserLoginDTO;
import com.example.redis.demo.exception.BusinessException;
import com.example.redis.demo.mapper.UserLoginLogMapper;
import com.example.redis.demo.mapper.UserMapper;
import com.example.redis.demo.model.User;
import com.example.redis.demo.request.RefreshTokenRequest;
import com.example.redis.demo.request.UpdateUserInfoRequest;
import com.example.redis.demo.request.UserLoginRequest;
import com.example.redis.demo.request.UserRegisterRequest;
import com.example.redis.demo.response.RefreshTokenResponse;
import com.example.redis.demo.response.UserAuthResponse;
import com.example.redis.demo.response.UserInfoUpdateResponse;
import com.example.redis.demo.response.UserRegisterResponse;
import com.example.redis.demo.service.UserService;
import com.example.redis.demo.util.JwtUtil;
import com.example.redis.demo.util.PasswordUtil;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.GeoOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;
import java.util.Date;
import java.util.Map;

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
        implements UserService {


    @Autowired
    private RedisTemplate<String, String> redisTemplate;


    @Autowired
    private UserLoginLogMapper userLoginLogMapper;


    @Override
    @Transactional(rollbackFor = Exception.class)
    public UserRegisterResponse register(UserRegisterRequest userRegisterRequest) {
        final User user = new User();
        user.setUsername(userRegisterRequest.getUserName());
        user.setPassword(PasswordUtil.encrypt(userRegisterRequest.getPassword()));
        user.setCreateUser(userRegisterRequest.getUserName());
        user.setCreateTime(new Date());
        user.setEmail(userRegisterRequest.getEmail());
        user.setStatus(0);
        int res = super.getBaseMapper().insert(user);
        if (res < 1) {
            throw BusinessException.error("注册失败");
        }
        return new UserRegisterResponse(Boolean.TRUE);
    }

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private TopicExchange userExchange;

    @Override
    public UserAuthResponse login(UserLoginRequest userLoginRequest) {
        //1.查询数据库有没有这个用户
        LambdaQueryWrapper<User> userLambdaQueryWrapper = new QueryWrapper<User>().lambda();
        userLambdaQueryWrapper.eq(User::getUsername, userLoginRequest.getUserName());
        User user = super.getBaseMapper().selectOne(userLambdaQueryWrapper);
        if (user == null) {
            throw BusinessException.error("用户不存在");
        }
        //获取密码比对
        String dbPassword = user.getPassword();
        boolean res = PasswordUtil.matches(userLoginRequest.getPassword(), dbPassword);
        if (!res) {
            throw BusinessException.error("密码错误");
        }
        //2.存在的话，颁发token,access_token假如是10分钟有效期,refresh_token 1day
        String accessToke = JwtUtil.generateToken(user.getId(), user.getUsername(), user.getEmail(), 10 * 60L);
        String refreshToken = JwtUtil.generateToken(user.getId(), user.getUsername(), user.getEmail(), 24 * 60 * 60L);
        final UserAuthResponse userAuthResponse = new UserAuthResponse();
        userAuthResponse.setAccessToken(accessToke);
        userAuthResponse.setRefreshToken(refreshToken);
        userAuthResponse.setAccessTokenExpiresIn(10 * 60L);
        userAuthResponse.setRefreshTokenExpiresIn(24 * 60 * 60L);
        //3.写缓存并设置过期时间
        ValueOperations<String, String> valueOperations = this.redisTemplate.opsForValue();
        valueOperations.set("u:" + user.getId() + ":access_token", accessToke, Duration.ofSeconds(10 * 60));
        valueOperations.set("u:" + user.getId() + ":refresh_token", refreshToken, Duration.ofDays(1));


        //6.写缓存
        //需要先清除zSet的value缓存
        String geoKey = "u:login:geo";
        ZSetOperations<String, String> zSetOperations = this.redisTemplate.opsForZSet();
        UserGeoDTO geoDTO = new UserGeoDTO();
        geoDTO.setUserId(user.getId());
        geoDTO.setUserName(user.getUsername());
        zSetOperations.remove(geoKey, geoDTO.getUserId() + ":" + geoDTO.getUserName());

        GeoOperations<String, String> geoOperations = this.redisTemplate.opsForGeo();
        RedisGeoCommands.GeoLocation<String> location = new
                RedisGeoCommands.GeoLocation<>(geoDTO.getUserId()
                + ":" + geoDTO.getUserName(),
                new Point(userLoginRequest.getLongitude(), userLoginRequest.getLatitude()));
        Long val = geoOperations.add(geoKey, location);
        System.out.println("加入的geo元素=" + val);

        //发送消息
        final UserLoginDTO userLoginDTO = new UserLoginDTO();
        userLoginDTO.setUserName(user.getUsername());
        userLoginDTO.setUserId(user.getId());
        userLoginDTO.setLongitude(userLoginRequest.getLongitude());
        userLoginDTO.setLatitude(userLoginRequest.getLatitude());
        userLoginDTO.setLoginTime(new Date());
        this.rabbitTemplate.convertAndSend(userExchange.getName(),
                "login.user", userLoginDTO, new MessagePostProcessor() {
                    @Override
                    public Message postProcessMessage(Message message) throws AmqpException {
                        System.out.println("发送登录成功消息=" + new String(message.getBody()));
                        return message;
                    }
                });
        return userAuthResponse;
    }

    /**
     * 鉴权的动作放到controller层做
     * 该接口需要登录之后才能访问
     *
     * @param updateUserInfoRequest
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public UserInfoUpdateResponse updateUserInfo(UpdateUserInfoRequest updateUserInfoRequest) {

        LambdaUpdateWrapper<User> userLambdaQueryWrapper = new UpdateWrapper<User>().lambda();
        userLambdaQueryWrapper.eq(User::getId, updateUserInfoRequest.getUserId());
        userLambdaQueryWrapper.set(User::getEmail, updateUserInfoRequest.getEmail());
        userLambdaQueryWrapper.set(User::getUpdateTime, new Date());
        userLambdaQueryWrapper.set(User::getUpdateUser, updateUserInfoRequest.getUserId() + "");
        int res = super.getBaseMapper().update(null, userLambdaQueryWrapper);
        if (res < 1) {
            throw BusinessException.error("更新失败");
        }
        return UserInfoUpdateResponse.success(updateUserInfoRequest.getEmail());
    }

    @Override
    public UserAuthResponse refreshToken(RefreshTokenRequest refreshTokenRequest) {
        Map<String, Object> claims = JwtUtil.parseToken(refreshTokenRequest.getRefreshToken());
        Long userId = Long.valueOf(claims.get("id").toString());
        ValueOperations<String, String> valueOperations = this.redisTemplate.opsForValue();
        String refreshToken = (String) valueOperations.get("u:" + userId + ":refresh_token");
        if (refreshToken == null) {
            throw BusinessException.errorOfCode("请先登录", "420");
        }

        Long ttl = valueOperations.getOperations().getExpire("u:" + userId + ":refresh_token");

        LambdaQueryWrapper<User> userLambdaQueryWrapper = new QueryWrapper<User>().lambda();
        userLambdaQueryWrapper.eq(User::getId, userId);
        User user = super.getBaseMapper().selectOne(userLambdaQueryWrapper);
        if (user == null) {
            throw BusinessException.errorOfCode("用户不存在", "500");
        }


        String accessToken = JwtUtil.generateToken(user.getId(), user.getUsername(), user.getEmail(), 10 * 60L);

        final RefreshTokenResponse.RefreshResult refreshTokenResponse = new RefreshTokenResponse.RefreshResult();
        refreshTokenResponse.setAccessToken(accessToken);
        refreshTokenResponse.setAccessTokenExpiresIn(10 * 60L);
        //写到缓存
        valueOperations.set("u:" + userId + ":access_token", accessToken, Duration.ofSeconds(10 * 60));
        UserAuthResponse authResponse = new UserAuthResponse();
        authResponse.setRefreshToken(refreshToken);
        authResponse.setAccessToken(accessToken);
        authResponse.setAccessTokenExpiresIn(10 * 60L);
        authResponse.setRefreshTokenExpiresIn(ttl);
        return authResponse;
    }
}
